package dpss.server.udp;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

import dpss.commons.CustomLogger;
import dpss.commons.Player;
import dpss.server.AccountDatabase;

/**
 * The thread to listen to any UDP requests
 * 
 * @author musab.mirza
 * 
 */
public class UDPListenerThread extends Thread {

	private static CustomLogger logger = CustomLogger.getInstance();

	private final AccountDatabase accountDatabase;
	private final Integer port;
	private DatagramSocket socket;

	/**
	 * Initialize the thread with the passed db
	 * 
	 * @param db
	 * @throws SocketException
	 */
	public UDPListenerThread(AccountDatabase db, Integer port)
			throws SocketException {
		super();
		this.accountDatabase = db;
		this.port = port;
		socket = new DatagramSocket(port);
	}

	/**
	 * The thread starter method
	 */
	public void run() {

		logger.log("Started UDP listener on port: " + port);

		// Start listening
		for (;;) {
			try {
				
				
				DatagramPacket receivedPacket = new DatagramPacket(new byte[UDPMessage.BYTE_SIZE], UDPMessage.BYTE_SIZE);
				socket.receive(receivedPacket);
				UDPMessage receivedMessage = UDPMessage.getFromBytes(receivedPacket.getData());
				UDPMessage sendMessage = null;
				DatagramPacket sendPacket = null;
				
				switch (receivedMessage.getMessage()){
				
					// getPlayersStatus requested
					case "getplayersinfo" :
						
						logger.log("Get player info request from: " + receivedPacket.getAddress().toString());
						
						sendMessage = new UDPMessage("getPlayersinfoResponse", accountDatabase.getPlayersInfo(), true, "");
						sendPacket = new DatagramPacket(sendMessage.getBytes(), sendMessage.getBytes().length, receivedPacket.getAddress(), receivedPacket.getPort());
						socket.send(sendPacket);
						break;
					
					// transferPlayer requested
					case "transferPlayer" :
						
						logger.log("Transfer player request from: " + receivedPacket.getAddress().toString());
						
						Player receivedPlayer = (Player) receivedMessage.getData();
					
						// Validation: Username already exists on this server
						if (accountDatabase.accountExists(receivedPlayer.getUsername())){
							sendMessage = new UDPMessage("transferPlayerResponse", null, false, "Username '"+receivedPlayer.getUsername()+"' already exists on the server");
							sendPacket = new DatagramPacket(sendMessage.getBytes(), sendMessage.getBytes().length, receivedPacket.getAddress(), receivedPacket.getPort());
							socket.send(sendPacket);
							break;
						}
						
						// Try to create the account and respond
						synchronized (this){
							try {
								receivedPlayer.setOnline(false);
								accountDatabase.addAccount(receivedPlayer);
								sendMessage = new UDPMessage("transferPlayerResponse", null, true, "");
								sendPacket = new DatagramPacket(sendMessage.getBytes(), sendMessage.getBytes().length, receivedPacket.getAddress(), receivedPacket.getPort());
								socket.send(sendPacket);
								logger.log("New player account created: " + receivedPlayer.getUsername());
								
							} catch (Exception e) {
								sendMessage = new UDPMessage("transferPlayerResponse", null, false, "Unable to create account for unknown reasons");
								sendPacket = new DatagramPacket(sendMessage.getBytes(), sendMessage.getBytes().length, receivedPacket.getAddress(), receivedPacket.getPort());
								socket.send(sendPacket);
							}
						}
						break;
					
					// Invalid request
					default:
						logger.log("ERROR - Invalid packet recieved");
						break;
				}
				
			} catch (IOException e) {
				logger.log("ERROR - Could not receive packet");
				e.printStackTrace();
			}

		}

	}
	
	


	/**
	 * Converts a player into bytes so it could be sent over the network
	 * 
	 * @param p
	 * @return
	 */
	public byte[] player2bytes(Player p){
		
		ByteArrayOutputStream bStream = new ByteArrayOutputStream();
		ObjectOutput oo;
		try {
			oo = new ObjectOutputStream(bStream);
			oo.writeObject(p);
			oo.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		byte[] serializedMessage = bStream.toByteArray();
		return serializedMessage;
	}
}
